using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using RoofRunner.Actors;
using RoofRunner.Base.Components;
using RoofRunner.Base.Camera;
using RoofRunner.Actors.ActorComponents.Rendering;


namespace RoofRunner.GameComponents.Rendering
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class RenderWorld : DrawableGameComponent
    {
        #region Fields
        List<AlphaMeshPart> alphaMeshParts = new List<AlphaMeshPart>();
        #endregion // Fields


        #region Properties
        public Color ClearColor { get; set; }
        private RoofRunnerGame RRGame { get { return (RoofRunnerGame)this.Game; } }

        public ActorMap Actors { get { return RRGame.Actors; } }
        public ICamera Camera { get { return RRGame.Camera; } }

        public LODSystem LODSystem { get; private set; }
        #endregion // Properties


        #region Constructors
        public RenderWorld(RoofRunnerGame game)
            : base(game)
        {
            this.LODSystem = new LODSystem();
        }
        #endregion // Constructors


        #region Methods
        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            this.ClearColor = Color.CornflowerBlue;

            base.Initialize();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            LODSystem.Update(gameTime, this);
            base.Update(gameTime);
        }

        #region Rendering
        public override void Draw(GameTime gameTime)
        {
            PreRender(gameTime);
            Render(gameTime);
            base.Draw(gameTime);
        }

        public void RenderModel(ModelRenderComponent rc)
        {
            Matrix[] modelTransforms = new Matrix[rc.Model.Bones.Count];
            rc.Model.CopyAbsoluteBoneTransformsTo(modelTransforms);

            /*
            be.FogColor = ClearColor.ToVector3();
            be.FogEnabled = true;
            be.FogStart = 10.0f;
            be.FogEnd = 40.0f;
            */

            float alpha = rc.Color.A / 255.0f;
            if (alpha == 1.0f)
            {// Model is not transparent
                BasicEffect be = (BasicEffect)rc.Model.Meshes[0].Effects[0];
                be.View = RRGame.Camera.View;
                be.Projection = RRGame.Camera.Projection;
                be.Alpha = 1.0f;
                
                be.TextureEnabled = rc.Texture != null;
                if (be.TextureEnabled) be.Texture = rc.Texture;

                foreach (var mm in rc.Model.Meshes)
                {
                    foreach (var mmp in mm.MeshParts)
                    {
                        be.World = modelTransforms[mm.ParentBone.Index] * rc.World;

                        GraphicsDevice.SetVertexBuffer(mmp.VertexBuffer, mmp.VertexOffset);
                        GraphicsDevice.Indices = mmp.IndexBuffer;
                        be.CurrentTechnique.Passes[0].Apply();
                        GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                            0, 0, mmp.NumVertices, mmp.StartIndex, mmp.PrimitiveCount);
                    }
                }
            }
            else
            {// Model is transparent
                foreach (var mm in rc.Model.Meshes)
                {
                    foreach (var mmp in mm.MeshParts)
                    {
                        Matrix w = modelTransforms[mm.ParentBone.Index] * rc.World;
                        Vector3 pos = w.Translation;

                        var camPos = this.Camera.Position;
                        float dist;
                        Vector3.Distance(ref pos, ref camPos, out dist);

                        this.alphaMeshParts.Add(new AlphaMeshPart(rc, mmp, w, dist, alpha));
                    }
                }
            }
        }


        #region Private-Rendering
        private void PreRender(GameTime gameTime)
        {
            GraphicsDevice.Clear(this.ClearColor);
            this.alphaMeshParts.Clear(); // Clear all non-opaque meshparts
            Actors.PreRender(gameTime);
        }

        private void Render(GameTime gameTime)
        {
            RenderOpaque(gameTime);
            if(this.alphaMeshParts.Count > 0) // If there are any transparent objects
                RenderTransparent(gameTime);
        }

        private void RenderOpaque(GameTime gameTime)
        {
            Game.GraphicsDevice.BlendState = BlendState.Opaque;
            Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            Game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
            // Render all actors (transparent actors are stored in alphameshParts)
            Actors.Render(gameTime, this);
        }

        private void RenderTransparent(GameTime gameTime)
        {
            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            //GraphicsDevice.BlendState = BlendState.NonPremultiplied;
            GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
            GraphicsDevice.RasterizerState = RasterizerState.CullNone;

            BasicEffect be = (BasicEffect)alphaMeshParts[0].MeshPart.Effect;
            be.View = RRGame.Camera.View;
            be.Projection = RRGame.Camera.Projection;

            var sorted = alphaMeshParts.OrderByDescending(x => x.Distance);
            foreach (var x in sorted)
            {
                be.TextureEnabled = x.RenderComponent.Texture != null;
                if (be.TextureEnabled)
                    be.Texture = x.RenderComponent.Texture;


                be.Alpha = x.Alpha;
                be.World = x.World;

                var mmp = x.MeshPart;

                GraphicsDevice.SetVertexBuffer(mmp.VertexBuffer, mmp.VertexOffset);
                GraphicsDevice.Indices = mmp.IndexBuffer;
                be.CurrentTechnique.Passes[0].Apply();
                GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                    0, 0, mmp.NumVertices, mmp.StartIndex, mmp.PrimitiveCount);
            }
        }
        #endregion // Private-Rendering
        #endregion // Rendering
        #endregion // Methods
    }
}
